/**  
 * All rights Reserved, Designed By http://www.pete-cat.com/
 * @Title:  ExceptionHandler.java   
 * @Package com.petecat.riskmanage.exception.handler   
 * @Description:TODO(用一句话描述该文件做什么)   
 * @author: 成都皮特猫科技     
 * @date:2017年9月5日 上午10:06:05   
 * @version V1.0 
 * @Copyright: 2017 www.pete-cat.com Inc. All rights reserved. 
 * 注意：本内容仅限于成都皮特猫信息技术有限公司内部传阅，禁止外泄以及用于其他的商业目
 */  
package com.petecat.ruleengine.core.exception.handler;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import org.springframework.web.util.WebUtils;

import com.alibaba.fastjson.JSON;
import com.petecat.ruleengine.core.aop.log.handler.LogStoreHandler;
import com.petecat.ruleengine.core.aop.log.model.LogDataModel;
import com.petecat.ruleengine.core.exception.CommonException;
import com.petecat.ruleengine.core.exception.CommonExceptionType;
import com.petecat.ruleengine.core.message.ApplicationMessage;
import com.petecat.ruleengine.protocol.global.Result;

/**   
 * @ClassName:  ExceptionHandler   
 * @Description:异常控制
 * @author: admin
 * @date:   2017年9月5日 上午10:06:05   
 */
public class ExceptionHandlerResolver extends ExceptionHandlerExceptionResolver{
	
	private Log log = LogFactory.getLog(ExceptionHandlerResolver.class);
	

	/**   
	 * <p>Title: doResolveHandlerMethodException</p>   
	 * <p>Description: </p>   
	 * @param request
	 * @param response
	 * @param handlerMethod
	 * @param exception
	 * @return ModelAndView  
	 * @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.web.method.HandlerMethod, java.lang.Exception)   
	 */  
	@Override
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response,
			HandlerMethod handlerMethod, Exception exception) {
		  log.error(exception.getMessage(),exception);
		  LogDataModel model =  LogStoreHandler.getLog();
		  ModelAndView modelAndView = new ModelAndView();;
		  boolean writeLog = false;
		try {
			  if(model == null) {
				  writeLog = true;
				  model = this.getLog(handlerMethod,request,exception);
			  }
			  boolean isResponseData = false;
			  if(handlerMethod != null) {
				  Method method = handlerMethod.getMethod();
				  if(handlerMethod.getBean() != null ) {
					  if(handlerMethod.getBean().getClass().isAnnotationPresent(RestController.class)
							  || handlerMethod.getBeanType().isAnnotationPresent(RestController.class)
							  || handlerMethod.getBeanType().isAnnotationPresent(ResponseBody.class)) {
						  isResponseData  = true;
					  }
				  }
				  if(!isResponseData && method!=null) {
					  if(handlerMethod.hasMethodAnnotation(ResponseBody.class)) {
						  isResponseData = true;
					  }
				  }
			  }
			  CommonException e = null;
			  if(exception instanceof CommonException){
				  e = (CommonException)exception;
			  }else if(exception.getCause() instanceof CommonException) {
				  e = (CommonException)exception.getCause();
			  }
			  Result<?> result = null;
			  if(e != null) {
				  CommonException realException = null;
				  if(e.getOriginalException() != null) {
					  realException = e.getOriginalException();
				  }
				  if(e.getType() == CommonExceptionType.JSONERROR_200
						  || e.getType() == CommonExceptionType.ERROR_OUTPUT_200) {
					  applyStatusCodeIfPossible(request, response,HttpStatus.OK.value());
				  }else if(e.getType() == CommonExceptionType.JSONERROR_402 
						  || e.getType() == CommonExceptionType.ERROR_OUTPUT_402) {
					  applyStatusCodeIfPossible(request, response,HttpStatus.PAYMENT_REQUIRED.value());
				  }else if(e.getType() == CommonExceptionType.JSONERROR_500 
						  || e.getType() == CommonExceptionType.ERROR_OUTPUT_500) {
					  applyStatusCodeIfPossible(request, response,HttpStatus.INTERNAL_SERVER_ERROR.value());
				  }
				  if( realException!= null) {
					   result = Result.error(e.getMessageType(),
							  realException.getMessage()); 
					   result.setCode(realException.getCode());
				  }else{
					   result = Result.error(e.getMessageType(),
								  e.getMessage()); 
					   result.setCode(e.getCode());
				  }
			  }else{//其他异常类型
				    applyStatusCodeIfPossible(request, response,HttpStatus.PAYMENT_REQUIRED.value());
				    String messageCode = "-1";
				    String message = null;
					if (exception instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {
						messageCode="9001";
					}
					else if (exception instanceof HttpRequestMethodNotSupportedException) {
						messageCode="9002";
					}
					else if (exception instanceof HttpMediaTypeNotSupportedException) {
						messageCode="9003";
					}
					else if (exception instanceof HttpMediaTypeNotAcceptableException) {
						messageCode="9003";
					}
					else if (exception instanceof MissingPathVariableException) {
						messageCode="9004";
					}
					else if (exception instanceof MissingServletRequestParameterException) {
						messageCode="9004";
					}
					else if (exception instanceof ServletRequestBindingException) {
						messageCode="9005";
					}
					else if (exception instanceof ConversionNotSupportedException) {
						messageCode="9003";
					}
					else if (exception instanceof TypeMismatchException) {
						messageCode="9006";
					}
					else if (exception instanceof HttpMessageNotReadableException) {
						messageCode="9002";
				    }
					else if (exception instanceof HttpMessageNotWritableException) {
						messageCode="9002";
					}
					else if (exception instanceof MethodArgumentNotValidException) {
						MethodArgumentNotValidException validException = (MethodArgumentNotValidException) exception;
						List<ObjectError> errors =validException.getBindingResult().getAllErrors();
						if(!CollectionUtils.isEmpty(errors)) {
							 message = errors.get(0).getDefaultMessage();
						}
						messageCode="9007";
					}
					else if (exception instanceof MissingServletRequestPartException) {
						messageCode="9008";
					}
					else if (exception instanceof BindException) {
						BindException realException = (BindException) exception;
						List<ObjectError> errors =realException.getBindingResult().getAllErrors();
						if(!CollectionUtils.isEmpty(errors)) {
							 message = errors.get(0).getDefaultMessage();
						}
						messageCode="9005";
					}
					else if (exception instanceof NoHandlerFoundException) {
						messageCode="9001";
					}
					else if (exception instanceof AsyncRequestTimeoutException) {
						messageCode="9009";
					}
					else if (exception instanceof ConstraintViolationException) {
						  ConstraintViolationException vexception = (ConstraintViolationException) exception;
						  if(!CollectionUtils.isEmpty(vexception.getConstraintViolations())) {
							   ConstraintViolation v =  vexception.getConstraintViolations().toArray(new ConstraintViolation[0])[0];
							   message = v.getMessage();
						  }
					  	  messageCode="9013";
					}
					 
					if(StringUtils.isBlank(message)) {
						result = Result.runtimeError(ApplicationMessage.getMessage(messageCode));
					}else {
						result = Result.runtimeError(message);
					}
					result.setCode(Integer.parseInt(messageCode));
				}
			  if(isResponseData) {//变成json
				  if( e != null) {
					  if(e.getType() == CommonExceptionType.JSONERROR_200
							  || e.getType() == CommonExceptionType.JSONERROR_402
							  || e.getType() == CommonExceptionType.JSONERROR_500) {
						  MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
						  modelAndView.setView(jsonView);
						  modelAndView.addObject("code", result.getCode());
						  modelAndView.addObject("message", result.getMessage());
					  }else if(e.getType() == CommonExceptionType.ERROR_OUTPUT_500 
							  || e.getType() == CommonExceptionType.ERROR_OUTPUT_402
							  || e.getType() == CommonExceptionType.ERROR_OUTPUT_200) {
						  modelAndView = null;
						  try {
							FileCopyUtils.copy(result.getMessage().getBytes("utf-8"), response.getOutputStream());
							this.writeVistResult(model,result.getCode(),result.getMessage());
						} catch (IOException e1) {
						}
					  }else {
						  applyStatusCodeIfPossible(request, response,HttpStatus.OK.value());
						  MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
						  modelAndView.setView(jsonView);
						  modelAndView.addObject("code", result.getCode());
						  modelAndView.addObject("message", result.getMessage());
						  this.writeVistResult(model,result.getCode(),result.getMessage());
					  }
				  }else {
					  applyStatusCodeIfPossible(request, response,HttpStatus.OK.value());
					  MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
					  modelAndView.setView(jsonView);
					  modelAndView.addObject("code", result.getCode());
					  modelAndView.addObject("message", result.getMessage());
					  this.writeVistResult(model,result.getCode(),result.getMessage());
				  }
			  }else {
				  applyStatusCodeIfPossible(request, response,HttpStatus.OK.value());
				  MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
				  modelAndView.setView(jsonView);
				  modelAndView.addObject("code", result.getCode());
				  modelAndView.addObject("message", result.getMessage());
				  this.writeVistResult(model,result.getCode(),result.getMessage());
			  }
		} finally {
			if(writeLog) {
				LogStoreHandler.writerLog(model);
			}
		}
		  return modelAndView;
	}
	/**   
	 * @Title: writeVistResult   
	 * @Description: TODO(这里用一句话描述这个方法的作用)   
	 * @param model
	 * @param code
	 * @param message
	 * @return void     
	 */
	private void writeVistResult(LogDataModel model, int code, String message) {
		if(model!=null) {
			try {
				model.setExecEndTime(System.nanoTime()/1000000);
				Result<?> result = new Result<>();
				result.setCode(code);
				result.setMessage(message);
				model.setResult(JSON.toJSONString(result));
			} catch (Exception e) {
			}
		}
	}
	/**   
	 * @Title: getLog   
	 * @Description: 获取日志
	 * @param handlerMethod
	 * @param request
	 * @param exception 
	 * @return LogDataModel     
	 */
	private LogDataModel getLog(HandlerMethod handlerMethod, HttpServletRequest request, Exception exception) {
		  LogDataModel model =  null;
		  if(handlerMethod!=null ) {
			 if(handlerMethod.getBeanType() != null ) {
				 model = LogStoreHandler.gernateInitLog(handlerMethod.getBeanType().getName(),
						 handlerMethod.getMethod().getName(),
						  request);
			 }else if(handlerMethod.getBean() != null) {
				 model = LogStoreHandler.gernateInitLog(handlerMethod.getBean().getClass().getName(),
						 handlerMethod.getMethod().getName(),
						  request);
			 }else {
					model = LogStoreHandler.gernateInitLog(handlerMethod.getClass().getName(),handlerMethod.getMethod().getName(),
							  request);
			 }
		  }else {
			  try {
				model = LogStoreHandler.gernateInitLog(null,null, request);
			} catch (Exception e1) {
			}
		  }
		  try {
				if (model != null) {
					if (exception != null) {
						StringWriter writer = new StringWriter();
						exception.printStackTrace(new PrintWriter(writer));
						model.setExceptionContent(writer.toString());
					}
				}
			}catch(Exception e) {
				logger.error(e.getMessage(),e);
			}
		  return model;
	}
	/**
	 * Apply the specified HTTP status code to the given response, if possible (that is,
	 * if not executing within an include request).
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param statusCode the status code to apply
	 * @see #determineStatusCode
	 * @see #setDefaultStatusCode
	 * @see HttpServletResponse#setStatus
	 */
	protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
		if (!WebUtils.isIncludeRequest(request)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Applying HTTP status code " + statusCode);
			}
			response.setContentType("text/html;charset=utf-8");
			response.setStatus(statusCode);
			request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
		}
	}

}
